using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Opc.Ua;
using System.Windows.Forms;
using Opc.Ua.Client;
using Opc.Ua.Client.Controls;
using OpcuaClient.Model;
using System.Xml.Linq;
using System.Threading;
using DataService.Helper;
using Newtonsoft.Json;
using Opc.Ua.Sample.Service;
using System.Data;

namespace OpcuaClient.Service
{
    public class CollectReadService
    {
        LogHelper LogHelper;
        public OpcConnectService OpcConnectService { get; set; }
        public Collection Collection { get; set; }
        public bool IsRead { get; set; }

        private event EventHandler m_ValueChange;
        private event EventHandler m_ReadError;
        /// <summary>
        /// Raised after a node is selected in the control.
        /// </summary>
        public event EventHandler AfterValueChanged { add { m_ValueChange += value; } remove { m_ValueChange -= value; } }
        /// <summary>
        /// Raised after a node is deleted in the control.
        /// </summary>
        public event EventHandler ReadError { add { m_ReadError += value; } remove { m_ReadError -= value; } }
        public CollectReadService() { }

        public CollectReadService(OpcConnectService connectService, Collection collection)
        {
            OpcConnectService = connectService;
            Collection = collection;
            LogHelper = new LogHelper(connectService.Server.Code);
        }
        #region Public Interface
        /// <summary>
        /// The view to use.
        /// </summary>
        public ViewDescription View { get; set; }

        /// <summary>
        /// 停止数据采集
        /// </summary>
        public void Stop()
        {
            IsRead = false;
            if (OpcReadThread != null) { OpcReadThread.Abort(); }
        }

        Thread OpcReadThread;
        /// <summary>
        /// 开始采集
        /// </summary>
        public void Read()
        {
            IsRead = true;
            InitReadConfig(Collection.Node);
            OpcReadThread = new Thread(() => {
                while (IsRead)
                {
                    if (Collection.Frequency == 0)
                    {
                        LogHelper.Error($"{OpcConnectService.Server.Name}-{Collection.Name}:采集频率不能为0！");
                        IsRead = false;
                        return;
                    }
                    ReadAttributes(Collection.Node);
                    Thread.Sleep(Collection.Frequency);
                }
                IsRead = false;
            });
            OpcReadThread.Start();
        }

        #region 读取准备

        // build list of attributes to read.
        ReadValueIdCollection nodesToRead = new ReadValueIdCollection();
        uint attributeId = 13;
        private void InitReadConfig(NodeId nodeId)
        {
            if (OpcConnectService.Session == null)
            {
                Stop();
                return;
            }

            //加载点位所有属性
            foreach (uint attributeId in Opc.Ua.Attributes.GetIdentifiers())
            {
                ReadValueId nodeToRead = new ReadValueId();
                nodeToRead.NodeId = nodeId;
                nodeToRead.AttributeId = attributeId;
                nodesToRead.Add(nodeToRead);
            }

            // read the attributes.
            DataValueCollection results = null;
            DiagnosticInfoCollection diagnosticInfos = null;
            OpcConnectService.Session.Read(
                null,
                0,
                TimestampsToReturn.Neither,
                nodesToRead,
                out results,
                out diagnosticInfos);

            //记录值属性
            for (int ii = 0; ii < results.Count; ii++)
            {
                // check for error.
                if (StatusCode.IsBad(results[ii].StatusCode))
                {
                    if (results[ii].StatusCode == StatusCodes.BadAttributeIdInvalid)
                    {
                        continue;
                    }
                }

                // add the metadata for the attribute.
                attributeId = nodesToRead[ii].AttributeId;

                string name = Attributes.GetBrowseName(attributeId);
                if (name == "Value")
                {
                    return;
                }
            }
        }
        #endregion

        string beforeValue;
        /// <summary>
        /// Reads the attributes for the node.
        /// </summary>
        public void ReadAttributes(NodeId nodeId)
        {
            try
            {
                if (NodeId.IsNull(nodeId))
                {
                    return;
                }

                // read the attributes.
                DataValueCollection results = null;
                DiagnosticInfoCollection diagnosticInfos = null;

                var session = OpcConnectService.Session;
                if (session == null)
                {
                    IsRead = false;
                    return;
                }
                RequestHeader header = new RequestHeader();
                session.Read(
                    header,
                    0,
                    TimestampsToReturn.Neither,
                    nodesToRead,
                    out results,
                    out diagnosticInfos);

                int valueId = (int)attributeId - 1;

                if (StatusCode.IsBad(results[valueId].StatusCode))
                {
                    if (results[valueId].StatusCode == StatusCodes.BadAttributeIdInvalid)
                    {
                        return;
                    }
                }

                if (StatusCode.IsBad(results[valueId].StatusCode))
                {
                    return;
                }
                else
                {
                    //ValueChange 事件
                    string itemValue = results[valueId]?.Value.ToString();
                    if (itemValue == "System.String[]")
                    {
                        itemValue = string.Join(",", ((object[])results[valueId]?.Value).Select(m => m.ToString()).ToArray());
                    }

                    if (!itemValue.Equals(beforeValue) && itemValue != null)
                    {
                        Collection.Value = itemValue;
                        Collection.CollectTime = DateTime.Now;
                        Collection.Status = true;

                        // raise event.
                        if (m_ValueChange != null)
                        {
                            m_ValueChange(Collection, new EventArgs());
                        }
                        beforeValue = itemValue;
                        LogHelper.Info($"{OpcConnectService.Server.Name}-{Collection.Name}:{itemValue}");
                        return;
                    }
                }
            }
            catch (Exception ex)
            {
                // raise event.
                if (m_ReadError != null) m_ReadError(ex, new EventArgs());
                LogHelper.Error($"{OpcConnectService.Server.Name}-{Collection.Name}:" + ex.Message+"\r\n"+ex.StackTrace);
            }
        }
        #endregion

        #region AttributeInfo Class
        /// <summary>
        /// The saved information for an attribute/property displayed in the control. 
        /// </summary>
        public class AttributeInfo
        {
            public ReadValueId NodeToRead;
            public DataValue Value;
        }
        #endregion

        #region Private Methods
        /// <summary>
        /// Reads the properties for the node.
        /// </summary>
        private void ReadProperties(NodeId nodeId)
        {
            try
            {
                // build list of references to browse.
                BrowseDescriptionCollection nodesToBrowse = new BrowseDescriptionCollection();

                BrowseDescription nodeToBrowse = new BrowseDescription();

                nodeToBrowse.NodeId = nodeId;
                nodeToBrowse.BrowseDirection = BrowseDirection.Forward;
                nodeToBrowse.ReferenceTypeId = Opc.Ua.ReferenceTypeIds.HasProperty;
                nodeToBrowse.IncludeSubtypes = true;
                nodeToBrowse.NodeClassMask = (uint)NodeClass.Variable;
                nodeToBrowse.ResultMask = (uint)BrowseResultMask.All;

                nodesToBrowse.Add(nodeToBrowse);

                // find properties.
                ReferenceDescriptionCollection references = ClientUtils.Browse(OpcConnectService.Session, View, nodesToBrowse, false);

                // build list of properties to read.
                ReadValueIdCollection nodesToRead = new ReadValueIdCollection();

                for (int ii = 0; references != null && ii < references.Count; ii++)
                {
                    ReferenceDescription reference = references[ii];

                    // ignore out of server references.
                    if (reference.NodeId.IsAbsolute)
                    {
                        continue;
                    }

                    ReadValueId nodeToRead = new ReadValueId();
                    nodeToRead.NodeId = (NodeId)reference.NodeId;
                    nodeToRead.AttributeId = Attributes.Value;
                    nodeToRead.Handle = reference;
                    nodesToRead.Add(nodeToRead);
                }

                if (nodesToRead.Count == 0)
                {
                    return;
                }

                // read the properties.
                DataValueCollection results = null;
                DiagnosticInfoCollection diagnosticInfos = null;

                OpcConnectService.Session.Read(
                    null,
                    0,
                    TimestampsToReturn.Neither,
                    nodesToRead,
                    out results,
                    out diagnosticInfos);

                ClientBase.ValidateResponse(results, nodesToRead);
                ClientBase.ValidateDiagnosticInfos(diagnosticInfos, nodesToRead);

                // add the results to the display.
                for (int ii = 0; ii < results.Count; ii++)
                {
                    ReferenceDescription reference = (ReferenceDescription)nodesToRead[ii].Handle;

                    TypeInfo typeInfo = TypeInfo.Construct(results[ii].Value);

                    // add the metadata for the attribute.
                    ListViewItem item = new ListViewItem(reference.ToString());
                    item.SubItems.Add(typeInfo.BuiltInType.ToString());

                    if (typeInfo.ValueRank >= 0)
                    {
                        item.SubItems[1].Text += "[]";
                    }

                    // add the value.
                    if (StatusCode.IsBad(results[ii].StatusCode))
                    {
                        item.SubItems.Add(results[ii].StatusCode.ToString());
                    }
                    else
                    {
                        item.SubItems.Add(results[ii].WrappedValue.ToString());
                    }

                    item.Tag = new AttributeInfo() { NodeToRead = nodesToRead[ii], Value = results[ii] };
                    item.ImageIndex = ClientUtils.GetImageIndex(OpcConnectService.Session, NodeClass.Variable, Opc.Ua.VariableTypeIds.PropertyType, false);

                }
            }
            catch (Exception ex)
            {
                // raise event.
                if (m_ReadError != null) m_ReadError(ex, new EventArgs());
                LogHelper.Error("Collect:" + ex.Message);
            }
        }
        #endregion
    }
}
